package com.webvihar.common.db;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;

import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.query.Query;
import com.webvihar.common.utils.Logger;

public class DbService {

	private static ObjectContainer objectContainer;
	private static EmbeddedConfiguration configuration;
	public static final int GLOBAL_QUERY_DEPTH = 1;
	
	public DbService(Context context) {
		DbAdapter.currentContext = (DbAdapter.currentContext == null) ? context : DbAdapter.currentContext;
	}
	
	public ObjectContainer GetDb() {
		objectContainer = objectContainer == null ? Db4oEmbedded.openFile(GetConfig(), DbAdapter.createDbFile()) : objectContainer;
		return objectContainer;
	}

	public EmbeddedConfiguration GetConfig() {
		configuration = new DbConfigurationBuilder().getConfiguration();
		return configuration;
	}

	public void setActivationDepth(int depth) {
		configuration.common().activationDepth(depth);
	}

	public ObjectContainer ObjectContainer() {
		return GetDb();
	}

	/**
	 * Save an entity to the DB. Entity must be marked with
	 * {@code IDomainEntity}. Remember to set the appropriate rules in the
	 * {@code DbConfigurationBuilder} class so that the data is persisted
	 * properly in child objects.
	 */
	public <T> void Save(IDomainEntity<T> entity) {
		try {
			ObjectContainer db = GetDb();
			db.store(entity);
			db.commit();
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
		}
	}

	/**
	 * Save List of entities of type T to DB. Each entity must be marked with
	 * {@code IDomainEntity}. Remember to set the appropriate rules in the
	 * {@code DbConfigurationBuilder} class so that the data is persisted
	 * properly in child objects.
	 */
	public <T> void Save(List<T> entity) {
		try {
			ObjectContainer db = GetDb();
			for (T iDomainEntity : entity) {
				db.store(iDomainEntity);
			}
			db.commit();
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
		}
	}
	
	/**
	 * Save List of entities of type T to DB Each entity must be marked with
	 * {@code IDomainEntity}. Remember to set the appropriate rules in the
	 * {@code DbConfigurationBuilder} class so that the data is persisted
	 * properly in child objects.
	 */
	public <T> void Delete(Class<T> entity) {
		try {
			ObjectContainer db = GetDb();
			List<T> results = db.query(entity);
			for (T iDomainEntity : results) {
				db.delete(iDomainEntity);
			}
			db.commit();
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
		}
	}
	
	/**
	 * Save entity of type T to DB entity must be marked with
	 * {@code IDomainEntity}. Remember to set the appropriate rules in the
	 * {@code DbConfigurationBuilder} class so that the data is persisted
	 * properly in child objects.
	 */
	public <T> void Delete(T entity) {
		try {
			ObjectContainer db = GetDb();
			List<T> results = db.queryByExample(entity);
			for (T iDomainEntity : results) {
				db.delete(iDomainEntity);
			}
			db.commit();
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
		}
	}

	/**
	 * Get List of entities of type T from DB. Make sure to use {@link
	 * DbService.setActivationDepth()} so that the appropriate depth is applied
	 * to the query. Otherwise, the data that is returned may not be hydrated
	 * down to the depth that is needed.
	 */
	public <T> List<T> Get(Class<T> entity) {
		try {
			ObjectContainer db = GetDb();
			return db.query(entity);
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
			return new ArrayList<T>();
		}
	}

	/**
	 * Get entity of type T containing id from DB. Make sure to use {@link
	 * DbService.setActivationDepth()} so that the appropriate depth is applied
	 * to the query. Otherwise, the data that is returned may not be hydrated
	 * down to the depth that is needed.
	 */
	public <T> T GetById(Class<T> entity, String fieldName, String id) {
		try {
			ObjectContainer db = GetDb();
			Query query = db.query();
			query.constrain(entity);
			query.descend(fieldName).constrain(id);
			ObjectSet<T> result = query.execute();
			if (result == null || result.size() == 0) { return null; }
			return result.get(0);
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
			return null;
		}
	}

	/**
	 * Remove the list @param toDelete from @param childList and then save @param
	 * parent. The parent and the two lists must be associated with each other.
	 * The removal does NOT delete the objects from the DB; instead, they are
	 * orphaned. If the developer wishes for the the objects to also be deleted
	 * from the DB, then set @param shouldDeleteChildFromDb to true. All
	 * entities MUST implement {@link IDomainEntity}. To ensure that child
	 * objects are updated and deleted, make sure to modify
	 * {@link DbConfigurationBuilder} so that it properly handles each entity.
	 * Remember to set the appropriate rules in the
	 * {@code DbConfigurationBuilder} class so that the data is deleted in child
	 * objects properly.
	 */
	public <T, U> void DeleteFromList(IDomainEntity<T> parent, List<U> childList, List<U> toDelete, boolean shouldDeleteChildFromDb) {
		try {
			childList.removeAll(toDelete);
			ObjectContainer db = GetDb();
			Save(parent);
			if (shouldDeleteChildFromDb) {
				for (U iDomainEntity : toDelete) {
					db.delete(iDomainEntity);
				}
			}
			db.commit();
		}
		catch (Exception e) {
			Logger.logError(Logger.LOG_TAG, this.getClass().getName(), e);
		}
	}
}